package com.mrzhou.data.util;

import com.fasterxml.jackson.core.type.TypeReference;
import com.mrzhou.core.tool.constants.CloudConstant;
import com.mrzhou.core.tool.utils.*;
import com.mrzhou.data.support.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;

import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.Predicate;

/**
 * 缓存工具类
 * @author qiang.zhou
 */
@Slf4j
public class CacheUtil {

    private static CacheManager cacheManager = SpringUtil.getBean(CacheManager.class);

    private static RedisService redisService = SpringUtil.getBean(RedisService.class);

    public static Cache getCache(String cacheName) {
        return cacheManager.getCache(cacheName);
    }

    /**
     * 获取CacheMange的缓存
     * @param cacheName 缓存名称，按模组分配 使用{@link CloudConstant} 中的常量
     * @param keyPrefix 缓存前缀，按业务区分
     * @param key key
     * @param valueLoader 缓存中不存在时需要重新载入的方式
     * @param clazz 将缓存中的数据反序列化成的对象class
     * @param <T> 泛型T
     * @return 缓存的数据
     */
    public static <T> T get(String cacheName, String keyPrefix, Object key, Class<T> clazz, Callable<T> valueLoader) {
        return get(cacheName, keyPrefix, key, Objects::nonNull, valueLoader, clazz, null);
    }

    public static <T> T get(String cacheName, String keyPrefix, Object key, TypeReference<T> ref, Callable<T> valueLoader) {
        return get(cacheName, keyPrefix, key, Objects::nonNull, valueLoader, null, ref);
    }

    private static <T> T get(String cacheName, String keyPrefix, Object key, Predicate<T> predicate, Callable<T> valueLoader, Class<T> clazz, TypeReference<T> ref) {
        if(Func.hasEmpty(cacheName, keyPrefix, key)) {
            return null;
        }

        try{
            Cache.ValueWrapper wrapper = getCache(cacheName).get(keyPrefix.concat(String.valueOf(key)));
            T value = null;
            if(null == wrapper) {
                T call = valueLoader.call();
                if(predicate.test(call)) {
                    getCache(cacheName).put(keyPrefix.concat(String.valueOf(key)), call);
                    value =call;
                }
            } else if (null != clazz) {
                value = BeanUtil.covertObject(wrapper.get(), clazz);
            } else {
                value = BeanUtil.covertObject(wrapper.get(), ref);
            }
            return value;
        } catch (Exception e) {
            // 将异常扔出
            throw new RuntimeException(e);
        }
    }


    /**
     *  使用Redis保存或者加载缓存的数据, 这个方法不可使用SpringBoot提供的CacheMange注解来管理缓存
     * @param key redis key
     * @param expire redis过期时间
     * @param predicate 保存至缓存的规则
     * @param valueLoader 数据加载
     * @return 直接返回json数据
     */
    public static <T> String getString(String key, Long expire, Predicate<T> predicate, Callable<T> valueLoader) {
        if (StringUtil.isBlank(key)) {
            return null;
        }
        try{
            // 检查缓存中是否用当前key
            String json = redisService.get(key);
            if(StringUtil.isBlank(json)) {
                T call = valueLoader.call();
                if(predicate.test(call)) {
                    redisService.set(key, call, expire);
                }
                return JacksonUtil.toJsonString(call);
            }
            return json;
        } catch (Exception e) {
            log.error("处理函数失败： {}", e);
        }
        return null;
    }

    /**
     *  使用Redis保存或者加载缓存的数据
     */
    public static <T> String getString(String key, Long expire, Callable<T> valueLoader) {
        return getString(key, expire, Objects::nonNull, valueLoader);
    }

    /**
     * json字串反序列化后返回
     * @param typeReference 反序列化的目标对象
     * @return
     */
    public static <T> T getObj(String key, Long expire, Predicate<T> predicate, Callable<T> valueLoader, TypeReference<T> typeReference) {
        String json = getString(key, expire, predicate, valueLoader);
        return JacksonUtil.parseObject(json, typeReference);
    }

    /**
     * json字串反序列化后返回
     * @param clazz 反序列化的目标对象
     * @return
     */
    public static <T> T getObj(String key, Long expire, Predicate<T> predicate, Callable<T> valueLoader, Class<T> clazz) {
        String json = getString(key, expire, predicate, valueLoader);
        return JacksonUtil.parseObject(json, clazz);
    }

    /**
     * 默认Callable返回不为null时, 保存到数据库中
     */
    public static <T> T getObj(String key, Long expire, Callable<T> valueLoader, TypeReference<T> typeReference) {
        String json = getString(key, expire, valueLoader);
        return JacksonUtil.parseObject(json, typeReference);
    }

    /**
     * 默认Callable返回不为null时, 保存到数据库中
     */
    public static <T> T getObj(String key, Long expire, Callable<T> valueLoader, Class<T> clazz) {
        String json = getString(key, expire, valueLoader);
        return JacksonUtil.parseObject(json, clazz);
    }

    public static <T> T getObj(String cacheName, String keyPrefix, String key, Long expire, Callable<T> valueLoader, Class<T> clazz) {
        String json = getString(getKey(cacheName, keyPrefix, key), expire, valueLoader);
        return JacksonUtil.parseObject(json, clazz);
    }

    /**
     * 获取key, 这里类似于SpringBoot的CacheManage中定义的缓存key
     * @param cacheName 所在的缓存空间，所在的应用，使用 {@link CloudConstant}中提供的常量类型
     * @param keyPrefix key的前缀，一般以业务命做前缀
     * @param key redis key
     * @return
     */
    public static String getKey(String cacheName, String keyPrefix, String key) {
        return cacheName + ":" + keyPrefix + key;
    }


}
